Skip to content

[runtime/simplex]]: add phantom order support for passive price and liquidity indexing#983

Draft
dharjeezy wants to merge 12 commits into
mainfrom
dami/phantom-order
Draft

[runtime/simplex]]: add phantom order support for passive price and liquidity indexing#983
dharjeezy wants to merge 12 commits into
mainfrom
dami/phantom-order

Conversation

@dharjeezy

Copy link
Copy Markdown
Contributor

Adds phantom order price and liquidity indexing to the intent coprocessor. The pallet gains two new extrinsics — register_phantom_order (permissionless) and set_phantom_bid_window (governance) — along with the CurrentPhantomOrder and PhantomBidWindow storage items and a phantom_bid_window() helper that mirrors the existing storage_deposit_fee() fallback pattern.

The SubQuery indexer is extended with handlePhantomOrderRegistered and handlePhantomBidPlaced handlers that decode each filler's user operation, extract proposed output amounts, and run a lightweight ERC-20 transfer simulation via state overrides to produce a simulationSuccess signal indexed alongside each bid.

closes #977

/// registered phantom order. Called by the intent coprocessor service.
#[pallet::call_index(7)]
#[pallet::weight(T::WeightInfo::register_phantom_order())]
pub fn register_phantom_order(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct, the phantom order should be generated by the runtime in the pallet hooks


Ok(())
}

@Wizdave97 Wizdave97 Jun 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no extrinsic to setup the phantom order details, like token pairs, chain for the order?

* one reverts, and `null` when the simulation could not be run (e.g. RPC error,
* unsupported node).
*/
async function simulateTokenTransfers(

@Wizdave97 Wizdave97 Jun 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct, simulate the bid as is, don't change the solver's balance, only state override needed is the block number so the order is not expired when simulating

@dharjeezy dharjeezy requested a review from Wizdave97 June 22, 2026 16:56
chain: &[u8],
pair: &PhantomTokenPair,
) -> H256 {
let mut preimage = Vec::new();

@Wizdave97 Wizdave97 Jun 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The phantom order should be an actual intent gateway order.

Comment thread sdk/packages/simplex/src/services/ContractInteractionService.ts
beneficiary: FixedBytes::from([0u8; 32]),
assets: vec![sol_types::TokenInfo {
token: FixedBytes::from(token_b_bytes),
amount: AlloyU256::from(min_output),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The output should be zero

Comment thread modules/pallets/intents-coprocessor/src/types.rs
Comment thread parachain/runtimes/nexus/src/ismp.rs Outdated

parameter_types! {
pub const IntentsStorageDepositFee: Balance = EXISTENTIAL_DEPOSIT * 10;
pub const IntentsPhantomOrderBidWindow: u32 = 15;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 hour in terms of blocks

params: [
{ from: solver, to: solver, data: callData },
"latest",
{ [gatewayAddress]: { code: "0x" } },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong, the bid should be validated against intent gatway's code.
the override required is inserting the phantom order commitment into the intent gateway's contract state

Comment thread modules/pallets/intents-coprocessor/src/types.rs Outdated
if (!simOk) continue

const tokenAddress = bytes32ToBytes20(output.token)
const balance = await getTokenBalance(evmUrl, tokenAddress, solver)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to fetch the filler's balance from the Aave's USDC/USDT vault and cngn yield vault also


const tokenAddress = bytes32ToBytes20(output.token)
const balance = await getTokenBalance(evmUrl, tokenAddress, solver)
if (balance === null || balance < outputAmount) continue

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we not storing the LP's balance?


const tokenAddress = bytes32ToBytes20(output.token)
const balance = await getTokenBalance(evmUrl, tokenAddress, solver)
if (balance === null || balance < outputAmount) continue

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is not necessary, if the simulation succeeded the filler must have some balance somewhere

Suggested change
if (balance === null || balance < outputAmount) continue

… and gateway state override for phantom order simulation
@dharjeezy dharjeezy requested a review from Wizdave97 June 23, 2026 18:09

// Use pallet-ismp's time provider so the deadline is a real chain timestamp
// (non-zero). Simulation uses block 0 (timestamp=0) so the check still passes.
let deadline_secs = <T as pallet_ismp::Config>::TimestampProvider::now().as_secs();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deadline is in blocks, I meant fetch the latest state machine height for the order chain and use that as the deadline

token_a: pair.token_a,
token_b: pair.token_b,
standard_amount: pair.standard_amount,
min_output: pair.min_output,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this min_output config, it's not useful

commitment: string,
): Promise<boolean> {
try {
const { slot1, slot2 } = requestCommitmentKey(commitment as HexString)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you using request commitment keys?
You are meant to override this storage in IntentGateway contract, please check how the contract works

    mapping(bytes32 => mapping(address => uint256)) public _orders;

jsonrpc: "2.0",
method: "eth_call",
params: [
{ from: solver, to: solver, data: callData },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct, is the to no meant to be the intent gateway contract?


const output = fillData.outputs[0]
const tokenAddress = bytes32ToBytes20(output.token)
const totalBalance = await getTotalSolverBalance(evmUrl, phantom.chain, tokenAddress, solver)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get solver balance across all configured evm chains

const totalBalance = await getTotalSolverBalance(evmUrl, phantom.chain, tokenAddress, solver)

prices.push(output.amount)
if (totalBalance > bestLpBalance) bestLpBalance = totalBalance

@Wizdave97 Wizdave97 Jun 24, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong, store the balance of each Lp that submitted a valid bid independently, also balances should be stored by token

// ERC-4626 vault addresses per chain, keyed by underlying token address (lowercase).
// Aave stata token addresses sourced from https://github.com/bgd-labs/aave-address-book
// Values are arrays because multiple vaults may wrap the same underlying token.
export const YIELD_VAULT_ADDRESSES: Record<string, Record<string, string[]>> = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add this to the builld pipeline like other addresses, don't harcode it here

},
// Optimism
"EVM-10": {
// USDC.e (bridged) → stataUSDC.e

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only native USDC

deadline: 0n,
nonce: BigInt(event.createdAt),
fees: 0n,
session: ZERO_ADDRESS,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This session address is not correct

// Reconstruct the Order with the same field values the pallet used when computing the commitment.
const ZERO_BYTES32 = `0x${"00".repeat(32)}` as HexString
const ZERO_ADDRESS = `0x${"00".repeat(20)}` as HexString
const phantomOrder: Order = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This order is wrong, fetch it from offchain storage

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[runtime/simplex]: Intent Coprocessor Price and Liquidity indexing

2 participants